/*
* Copyright (c) 2010-2011 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: Direct viewfinder for Camerality project.
*              Handles initializing camera and setting up the viewfinder
*              in a window.
*
*/

#include <coemain.h>
#include <e32cmn.h>
#include <e32des8.h>
#include <f32file.h>
#include <math.h>
#include <pathinfo.h>
#include <ecamusecasehintcustomapi.h>
#include "cameralitytraces.h"
#include "cameralityviewfinder.h"

namespace
    {
#ifdef CAMERALITY_TRACE_ON
    // State names for debugging purposes.
    static const char* const VIEWFINDER_STATE_NAMES[] =
        {
        "Idle",
        "Ready",
        "Running"
        };
    static const char* const ASPECT_RATIO_NAMES[] =
        {
        "4 by 3",
        "16 by 9",
        "11 by 9"
        };
#endif
    static const int KStringMaxLength = 256;
    
    typedef struct
    {
        TSize iResolution;
        TReal iFrameRate;
        TInt iVideoBitRate;
        TInt iAudioBitRate;
        TText8 iVideoFileMimeType[KStringMaxLength];
        TText8 iVideoCodecMimeType[KStringMaxLength];
        TText8 iPreferredSupplier[KStringMaxLength];
        TFourCC iAudioFourCCType;
    } TVideoQualityInfo;
    
    /** 
     * Video quality infos.
     * Indexed by AspectRatio enum values.
     */
    static const TVideoQualityInfo KVideoQualityInfos[3] =
        {
        // 4:3 quality (QVGA)
            {
            TSize(320, 240),
            30.0,
            768000,
            48000,
            "video/mp4",
            "video/mp4v-es; profile-level-id=3",
            "Nokia",
            TFourCC(_L8(" AAC"))
            },
        // 16:9 quality (NHD)
            {
            TSize(640, 352),
            30.0,
            3000000,
            96000,
            "video/mp4",
            "video/mp4v-es; profile-level-id=4",
            "Nokia",
            TFourCC(_L8(" AAC"))
            },
        // 11:9 quality (QCIF)
            {
            TSize(176, 144),
            15.0,
            60000,
            12200,
            "video/3gpp",
            "video/H263-2000",
            "Nokia",
            TFourCC(_L8(" AMR"))
            }
        };

    _LIT(KTempFileName, "test.vid");
    }


/*!
* Two-phased constructor.
* @return Viewfinder instance.
*/
CCameralityViewfinder* CCameralityViewfinder::NewL()
    {
    CCameralityViewfinder* self = new (ELeave) CCameralityViewfinder();
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

/*!
* Constructor.
*/
CCameralityViewfinder::CCameralityViewfinder()
    : iCamera(NULL),
      iVideoRecorder(NULL),
      iObserver(NULL),
      iState(EIdle)
    {
    }

/*!
* Second phase constructor.
*/
void CCameralityViewfinder::ConstructL()
    {
    // Create CCamera instance.
    // Will leave on emulator with -5 (KErrNotSupported).
    TRAP_IGNORE(iCamera = CCamera::New2L(*this, 0, 0));

    iVideoRecorder = CVideoRecorderUtility::NewL(*this);
    }

/*!
* Destructor.
*/
CCameralityViewfinder::~CCameralityViewfinder()
    {
    TRACE_ENTER();
    Release();
    delete iCamera;
    delete iVideoRecorder;
    TRACE_EXIT();
    }

/*!
* Handle ECAM event.
* @param aEvent Event id and status.
*/
void CCameralityViewfinder::HandleEvent(const TECAMEvent& aEvent)
    {
    TRACE_ENTER();
    TRACE_ARG("[Camerality] Event uid:0x%08x status:%d",
              aEvent.iEventType.iUid, aEvent.iErrorCode);

    // Reserve complete event
    if(aEvent.iEventType == KUidECamEventReserveComplete)
        {
        if (!CheckError(aEvent.iErrorCode))
            {
            iCamera->PowerOn();
            }
        }
    // Power on complete event
    else if(aEvent.iEventType == KUidECamEventPowerOnComplete)
        {
        if (!CheckError(aEvent.iErrorCode))
            {
            // We are ready to start            
            SetState(EReady);
            Start(*iWindow, iRect);
            }
        }
    // Camera released event
    else if(aEvent.iEventType == KUidECamEventCameraNoLongerReserved)
        {
        SetState(EIdle);
        }
    TRACE_EXIT();
    }

void CCameralityViewfinder::ViewFinderReady(MCameraBuffer& aCameraBuffer,
                                            TInt /*aError*/)
    {
    // Not used
    aCameraBuffer.Release();
    }

void CCameralityViewfinder::ImageBufferReady(MCameraBuffer& aCameraBuffer,
                                             TInt /*aError*/)
    {
    // Not used
    aCameraBuffer.Release();
    }

void CCameralityViewfinder::VideoBufferReady(MCameraBuffer& aCameraBuffer,
                                             TInt /*aError*/)
    {
    // Not used
    aCameraBuffer.Release();
    }

/*!
* Opening video is complete, proceed with preparing video
* if no errors were encountered.
*/
void CCameralityViewfinder::MvruoOpenComplete(TInt aError)
    {
    TRACE_ENTER();
    if (!CheckError(aError))
        {
        if (State() == EReady)
            {
            Prepare();
            }
        }
    }

/*!
* Preparing video is complete, proceed with starting the viewfinder
* if no errors were encountered.
*/
void CCameralityViewfinder::MvruoPrepareComplete(TInt aError)
    {
    TRACE_ENTER();
    if (!CheckError(aError))
        {
        if (State() == EReady)
            {
            StartNow();
            }
        }
    TRACE_EXIT();
    }

void CCameralityViewfinder::MvruoRecordComplete(TInt /*aError*/)
    {
    TRACE_VISIT();
    }

void CCameralityViewfinder::MvruoEvent(const TMMFEvent& /*aEvent*/)
    {
    TRACE_VISIT();
    }

/*!
* Set observer for viewfinder state and errors.
* @param aObserver Observer. Only one supported at a time.
*/
void CCameralityViewfinder::SetObserver(MCameralityViewfinderObserver* aObserver)
    {
    iObserver = aObserver;
    }

/*!
* Current state of viewfinder.
*/
CCameralityViewfinder::TViewfinderState CCameralityViewfinder::State() const
    {
    return iState;
    }

/*!
* Start viewfinder.
* @param aWindow Window where the live viewfinder will be rendered.
* @param aRect Size of the viewfinder. May be adjusted by ECAM.
*/
void CCameralityViewfinder::Start(RWindowBase& aWindow,
                                  TRect& aRect)
    {
    TRACE_ENTER();

    TRACE_ARG("[Camerality] requested vf rect: tl:(%d,%d) size(%dx%d)",
              aRect.iTl.iX, aRect.iTl.iY,
              aRect.Size().iWidth, aRect.Size().iHeight);
    iWindow = &aWindow;
    iRect = aRect;

    if (State() == EIdle)
        {
        Initialize();
        }
    else
        {
        if (State() == EReady)
            {
            // First prepare camera for video capture.
            // Select video quality with proper aspect ratio.
            // Using the lowest quality saves some GPU resources.
            //
            // Preparing needs to be done for 2 reasons:
            // a) Video mode with low quality saves some GPU resources.
            //    (Default mode is still mode when reserving camera.)
            // b) To be able to get full screen viewfinder with 16:9
            //     viewfinder aspect ratio. Unless we preparing, ECAM
            //    defaults to 4:3 aspect ratio and adjusts the
            //    viewfinder rect to match that.
            Open();
            }
        else // State() == ERunning
            {
            TRACE("[Camerality] Restarting viewfinder..")
            Stop();
            Start(aWindow, aRect);
            }
        }
    TRACE_EXIT();
    }

/*!
* Stop viewfinder.
*/
void CCameralityViewfinder::Stop()
    {
    TRACE_ENTER();
    if (State() == ERunning)
        {
        iCamera->StopViewFinder();
        iVideoRecorder->Close();
        iWindow = NULL;
        iRect = TRect();
        SetState(EReady);
        }
    TRACE_EXIT();
    }

/*!
* Release all camera resources.
*/
void CCameralityViewfinder::Release()
    {
    TRACE_ENTER();
    Stop();
    if (State() == EReady)
        {
        iVideoRecorder->Close();
        iCamera->Release();
        SetState(EIdle);
        }
    TRACE_EXIT();
    }

/*!
* Set new state for viewfinder.
* @param aState New state to enter. If state actually changes,
* observer ViewfinderStateChanged() is called with old and new state.
*/
void CCameralityViewfinder::SetState(TViewfinderState aState)
    {
    if (iState != aState)
        {
        TRACE_ARG("[Camerality] setting vf state from [%s] to [%s]",
                  VIEWFINDER_STATE_NAMES[iState],
                  VIEWFINDER_STATE_NAMES[aState]);

        TViewfinderState oldState(iState);
        iState = aState;
        if (iObserver)
            {
            iObserver->ViewfinderStateChanged(oldState, iState);
            }
        }
    }

/*!
* Check error code.
* If error detected, release resources and report error to observer.
* @param aError Error code.
* @return True if it was a real error, false if no error detected.
*/
TBool CCameralityViewfinder::CheckError(TInt aError)
    {
    TBool detected(aError != KErrNone);

    if (detected)
        {
        TRACE_ARG("[Camerality] Viewfinder error: %d", aError);
        Release();
        delete iCamera;
        iCamera = NULL;

        if (iObserver)
            {
            iObserver->ViewfinderError(aError);
            }
        }
    return detected;
    }

/*!
* Hint use case and reserve camera if needed. 
*/
void CCameralityViewfinder::Initialize()
    {    
    TRACE_ENTER();
    if (State() == EIdle)
        {
        if (iCamera)
            {
            HintUseCase();
            iCamera->Reserve();
            // Continue on reserve complete event.
            }
        }    
    TRACE_EXIT();
    }
        
/*!
* Hint our use case (video capturing) to camera.
* Speeds up startup and avoids some resources peaks during
* reserve / prepare sequence.
*/
void CCameralityViewfinder::HintUseCase()
    {
    MCameraUseCaseHint* api = 
            static_cast <MCameraUseCaseHint*>(
                    iCamera->CustomInterface(KCameraUseCaseHintUid));

    MCameraUseCaseHint::TVideoCodec codec = MCameraUseCaseHint::ECodecUnknown;
    MCameraUseCaseHint::TVideoProfile profile = MCameraUseCaseHint::EProfileUnknown;
    
    TAspectRatio ratio(AspectRatio(iRect.Size()));
    switch (ratio)
        {
        case ERatio4by3:
            codec = MCameraUseCaseHint::ECodecMpeg4;
            profile = MCameraUseCaseHint::EProfileMPEG4SpL3;                        
            break;
        case ERatio16by9:
            codec = MCameraUseCaseHint::ECodecMpeg4;
            profile = MCameraUseCaseHint::EProfileMPEG4SpL4a;                        
            break;
        case ERatio11by9:   
            codec = MCameraUseCaseHint::ECodecH263;
            profile = MCameraUseCaseHint::EProfileH263P0L10;
            break;
        }

    if (api)
        {
        // Try to hint our use case. Failing is non-fatal.
        TRAPD(status, {
            api->HintDirectVideoCaptureL(
                codec, profile, KVideoQualityInfos[ratio].iResolution);
            });
        TRACE_ARG("[Camerality] Status hinting use case: %d", status);
        }
    }

/*!
* Open video file.
* Needs to be done before we can prepare for video recording.
*/
void CCameralityViewfinder::Open()
    {
    TRACE_ENTER();
    TAspectRatio ratio(AspectRatio(iRect.Size()));
    const TVideoQualityInfo& info = KVideoQualityInfos[ratio];

    TUid controllerId;
    TUid formatId;

    TRAPD(error,
        {
        // Find the video controller id and video format id.
        FindControllerL(controllerId, formatId);

        TRACE("Open file..");
        // Try to open new video file with given parameters.
        TFileName filePath = PathInfo::PhoneMemoryRootPath();
        filePath.Append(PathInfo::VideosPath());
        filePath.Append(KTempFileName);

        TPtrC8 codecMime(info.iVideoCodecMimeType);

        iVideoRecorder->OpenFileL(
                filePath, iCamera->Handle(),
                controllerId, formatId,
                codecMime, info.iAudioFourCCType);
        });

    CheckError(error);
    TRACE_EXIT();
    }

/*!
* Prepare for video recording.
*/
void CCameralityViewfinder::Prepare()
    {
    TRACE_ENTER();
    TAspectRatio ratio(AspectRatio(iRect.Size()));
    const TVideoQualityInfo& info = KVideoQualityInfos[ratio];

    TRAPD(error,
        {
        iVideoRecorder->SetVideoFrameSizeL(info.iResolution);
        iVideoRecorder->SetVideoFrameRateL(info.iFrameRate);
        iVideoRecorder->SetVideoBitRateL(info.iVideoBitRate);
        iVideoRecorder->SetAudioEnabledL(false);
        iVideoRecorder->Prepare();
        });
    CheckError(error);
    TRACE_EXIT();
    }

/*!
* Start the viewfinder.
*/
void CCameralityViewfinder::StartNow()
    {
    TRACE_ENTER();
    CWsScreenDevice* device = CCoeEnv::Static()->ScreenDevice();
    RWsSession& session = CCoeEnv::Static()->WsSession();
    TRect rect = iRect;
    TRAPD(error, iCamera->StartViewFinderDirectL(session, *device, *iWindow, iRect));
    TRACE_ARG("[Camerality] ECAM adjusted vf rect: tl:(%d,%d) size(%dx%d)",
              rect.iTl.iX, rect.iTl.iY,
              rect.Size().iWidth, rect.Size().iHeight);
    if (!CheckError(error))
        {
        SetState(ERunning);
        }
    TRACE_EXIT();
    }

/*!
* Find video controller UID and video format UID needed for preparing video.
* @param [out] controllerId Controller id if one could be found, KNullUid otherwise.
* @param [out] formatId Format id if one could be found, KNullUid otherwise.
*/
void CCameralityViewfinder::FindControllerL(TUid& controllerId,
                                            TUid& formatId)
{
    TRACE_ENTER();
    TAspectRatio ratio(AspectRatio(iRect.Size()));
    const TVideoQualityInfo& info = KVideoQualityInfos[ratio];
    
    // Retrieve a list of possible controllers from ECOM.
    // Controller must support recording the requested mime type.
    // Controller must be provided by preferred supplier.
    controllerId = KNullUid;
    formatId = KNullUid;

    CMMFControllerPluginSelectionParameters* cSelect(NULL);
    CMMFFormatSelectionParameters* fSelect(NULL);
    RMMFControllerImplInfoArray controllers;

    cSelect = CMMFControllerPluginSelectionParameters::NewLC();
    fSelect = CMMFFormatSelectionParameters::NewLC();

    TRACE("Convert filemime to descriptor..");
    TPtrC8 fileMime(info.iVideoFileMimeType);
    TRACE("Convert supplier to descriptor..");
    TBuf<KStringMaxLength> supplier;
    supplier.Copy(TPtrC8(info.iPreferredSupplier));

    TRACE("Search for implementations..");
    fSelect->SetMatchToMimeTypeL(fileMime);
    cSelect->SetRequiredRecordFormatSupportL(*fSelect);
    cSelect->SetPreferredSupplierL(supplier,
                                   CMMFPluginSelectionParameters::EOnlyPreferredSupplierPluginsReturned);
    cSelect->ListImplementationsL(controllers);
    CleanupResetAndDestroyPushL(controllers);

    if (controllers.Count() <= 0) {
        TRACE("Zero controllers found!");
        User::Leave(KErrNotFound);
    }

    // Inquires the controller about supported formats.
    // We use the first controller found having index 0.
    const RMMFFormatImplInfoArray& formats = controllers[0]->RecordFormats();

    // Get the first format that supports our mime type.
    int count(formats.Count());
    for (int i=0; i<count; i++) {
        if (formats[i]->SupportsMimeType(fileMime)) {
            TRACE("Found controller..");
            // Store the format UID
            formatId = formats[i]->Uid();
            // Store the controller UID
            controllerId = controllers[0]->Uid();
            break;
        }
    }

    // Check that we found the uids.
    if (controllerId == KNullUid) {
        TRACE("No matching controller found!");
        User::Leave(KErrNotFound);
    }

    CleanupStack::PopAndDestroy(3); // controllers, fSelect, cSelect
    TRACE_EXIT();
}

/*!
* Check what is the aspect ratio of given size.
* Ratios of 4:3, 16:9 and 11:9 are supported.
*/
CCameralityViewfinder::TAspectRatio
CCameralityViewfinder::AspectRatio(const TSize& size)
    {
    static const double ratio16by9 = 16.0f / 9.0f;
    static const double ratio4by3 = 4.0f / 3.0f;
    static const double ratio11by9 = 11.0f / 9.0f;

    double ratio = (1.0f * size.iWidth) / (1.0f * size.iHeight);

    TRACE_ARG("[Camerality] 4 by 3 ratio:        %f", ratio4by3);
    TRACE_ARG("[Camerality] 16 by 9 ratio:       %f", ratio16by9);
    TRACE_ARG("[Camerality] 11 by 9 ratio:       %f", ratio11by9);
    TRACE_ARG("[Camerality] Size (%d, %d) ratio: %f",
              size.iWidth, size.iHeight, ratio);

    double delta4by3(fabs(ratio4by3-ratio));
    double delta16by9(fabs(ratio16by9-ratio));
    double delta11by9(fabs(ratio11by9-ratio));

    double minimum = fmin(delta4by3, fmin(delta16by9, delta11by9));

    TAspectRatio aspect = ERatio4by3;
    if (minimum == delta4by3)
        {
        aspect = ERatio4by3;
        }
    else if (minimum == delta16by9)
        {
        aspect = ERatio16by9;
        }
    else {
        aspect = ERatio11by9;
        }
    TRACE_ARG("[Camerality] Determined aspect ratio [%s]", ASPECT_RATIO_NAMES[aspect]);
    return aspect;
    }

// End of file
